/**
 * Copyright 2014-2020  the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.upb.webserver.common.filter;

import com.upb.webserver.common.enums.PlatformEnum;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.login.LoginAccount;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.HttpRequestTools;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.common.tools.WebServerTools;
import com.upb.webserver.dao.entity.TbDeveloper;
import com.upb.webserver.dao.entity.TbManager;
import com.upb.webserver.dao.entity.TbSecurity;
import com.upb.webserver.dao.mapper.TbDeveloperMapper;
import com.upb.webserver.dao.mapper.TbManagerMapper;
import com.upb.webserver.dao.mapper.TbSecurityMapper;
import com.upb.webserver.dao.mapper.TbTokenMapper;
import com.upb.webserver.token.TokenService;
import lombok.extern.log4j.Log4j2;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.util.ParameterMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;


/**
 * validate code before login.
 */
@Log4j2
@Component
@Order(-1001)
@WebFilter(filterName = "validateCodeFilter", urlPatterns = "/*")
public class ValidateCodeFilter implements Filter {
    @Autowired
    private TokenService tokenService;
    @Autowired
    private TbTokenMapper tbTokenMapper;
    @Autowired
    private TbSecurityMapper securityMapper;
    @Autowired
    private TbDeveloperMapper developerMapper;
    @Autowired
    private TbManagerMapper managerMapper;

    private static final String LOGIN_METHOD = "post";
    private static final List<String> CHECK_CODE_URI = Arrays.asList("/account/login", "/developer/register");

    /**
     * do filter.
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse rsp = (HttpServletResponse) response;
        String uri = HttpRequestTools.getUri(req).toLowerCase();
        String isOpenAPI = req.getHeader("isOpenAPI");
        //is login
        if (CHECK_CODE_URI.contains(uri) && LOGIN_METHOD.equalsIgnoreCase(req.getMethod())) {
            if (isOpenAPI != null && isOpenAPI.equalsIgnoreCase("true")) {
                try {
                    request = checkSecu(req);
                    String account = request.getParameter("accountJsonBase64");
                    log.info("try log-->" + account);
                } catch (WebServerException ex) {
                    WebServerTools.responseRetCodeException(rsp, ex.getRetCode());
                    return;
                }

            } else {
                try {
                    validateCode(req);
                } catch (WebServerException ex) {
                    WebServerTools.responseRetCodeException(rsp, ex.getRetCode());
                    return;
                } finally {
                    //remove token
                    tbTokenMapper.deleteByToken(req.getHeader("token"));
                }
            }
        }
        chain.doFilter(request, response);
    }


    /**
     * validate code.
     */
    private void validateCode(HttpServletRequest request) {
        String tokenInHeard = request.getHeader("token");
        String codeInRequest = request.getParameter("checkCode");
        log.info("validateCode. tokenInHeard:{} codeInRequest:{}", tokenInHeard, codeInRequest);

        if (StringUtils.isBlank(codeInRequest)) {
            throw new WebServerException(ConstantCode.CHECK_CODE_NULL);
        }
        if (StringUtils.isBlank(tokenInHeard)) {
            throw new WebServerException(ConstantCode.INVALID_CHECK_CODE);
        }
        String code = null;
        try {
            code = tokenService.getValueFromToken(tokenInHeard);
        } catch (WebServerException wex) {
            log.warn("fail validateCode by WebServerException", wex);
            if (wex.getRetCode().getCode().equals(ConstantCode.TOKEN_EXPIRE.getCode())) {
                throw new WebServerException(ConstantCode.CHECK_CODE_EXPIRE);
            }
        }
        if (!codeInRequest.equalsIgnoreCase(code)) {
            log.warn("fail validateCode. realCheckCode:{} codeInRequest:{}", code,
                    codeInRequest);
            throw new WebServerException(ConstantCode.INVALID_CHECK_CODE);
        }
    }

    private RequestFacade checkSecu(HttpServletRequest request) {
        String platform = request.getParameter("platform");
        String appid = request.getParameter("appid");
        String checkSum = request.getParameter("checksum");

        TbSecurity tbSecurity = null;
        String pwd = null;
        //login account info
        LoginAccount loginAccount = new LoginAccount();

        if (platform.equalsIgnoreCase(PlatformEnum.DEVELOPER.getId() + "")) {
            TbDeveloper tbDeveloper = developerMapper.getDeveloperByAppID(platform, appid);
            String account = tbDeveloper.getAccount();
            loginAccount.setAccount(account);
            loginAccount.setPlatform(PlatformEnum.DEVELOPER.getId());
            pwd = tbDeveloper.getPassword();

        } else if (platform.equalsIgnoreCase(PlatformEnum.DEV_OPS.getId() + "")) {
            TbManager tbManager = managerMapper.getManagerByAppID(platform, appid);
            String account = tbManager.getAccount();
            loginAccount.setAccount(account);
            loginAccount.setPlatform(PlatformEnum.DEV_OPS.getId());
            pwd = tbManager.getPassword();
        }
        tbSecurity = securityMapper.getSecurityByRelatedKey(loginAccount.getPlatform() + "", loginAccount.getAccount());

        if (tbSecurity == null) {
            throw new WebServerException(ConstantCode.INVALID_APPID);
        }

        String tarCheckSum = DigestUtils.md5DigestAsHex((tbSecurity.getSecurity() + "#$#" + appid).getBytes());
        if (checkSum == null || !checkSum.equalsIgnoreCase(tarCheckSum)) {
            throw new WebServerException(ConstantCode.INVALID_SECURITY);
        }

        //login account info base64
        String json = JsonUtils.objToString(loginAccount);
        String accountJsonBase64 = Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));

        ModifyRequest modifyRequest = new ModifyRequest((RequestFacade) request);
        modifyRequest.setParam("accountJsonBase64", accountJsonBase64);
        modifyRequest.setParam("accountPwd", pwd);

        return modifyRequest.orgReq;
    }

    class ModifyRequest {

        private RequestFacade orgReq;

        public ModifyRequest(RequestFacade req) {
            orgReq = req;
        }

        public void setParam(String key, String value) {
            ParameterMap<String, String[]> map = (ParameterMap) orgReq.getParameterMap();
            map.setLocked(false);
            map.put(key, new String[]{value});
            map.setLocked(true);

            try {
                Class CRequestFacade = RequestFacade.class;
                Field filed = CRequestFacade.getDeclaredField("request");
                filed.setAccessible(true);
                org.apache.catalina.connector.Request tagReq = (org.apache.catalina.connector.Request) filed.get(orgReq);

                Class CRequest = org.apache.catalina.connector.Request.class;
                filed = CRequest.getDeclaredField("coyoteRequest");
                filed.setAccessible(true);
                org.apache.coyote.Request coyoteRequest = (org.apache.coyote.Request) filed.get(tagReq);
                coyoteRequest.getParameters().addParameter(key, value);
            } catch (Exception ex) {
                log.error("fill params error-->", ex);
            }

        }
    }


}

